package com.blacksea.authserver.service.manager;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.blacksea.authserver.dal.dataobject.CodeDO;
import com.blacksea.authserver.dal.dataobject.TokenDO;
import com.blacksea.authserver.dal.enumclass.CodeStatusEnum;
import com.blacksea.authserver.dal.enumclass.TokenStatusEnum;
import com.blacksea.authserver.service.model.MixTokenDTO;
import com.blacksea.authserver.service.repository.CodeRepository;
import com.blacksea.authserver.service.repository.TokenRepository;
import com.blacksea.authserver.util.DateUtil;
import com.blacksea.authserver.util.RandomUtil;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

import java.util.Date;

import javax.annotation.Resource;

/**
 * TokenManagerImpl
 *
 * @author blacksea3(jxt) 2021/4/11 20:48
 */
@Service(value = "tokenManager")
@Transactional
public class TokenManagerImpl implements TokenManager {

    private static final int MAX_AUTHORIZATION_TOKEN_TRY_TIME = 10;

    private static final int MAX_REFRESH_TOKEN_TRY_TIME = 10;

    private static final int EXPIRE_SECONDS = 1800;

    private static final int TOKEN_LENGTH = 32;

    private static final String TOKEN_TYPE = "REFRESH";

    @Resource(name = "tokenRepository")
    private TokenRepository tokenRepository;

    @Resource(name = "codeRepository")
    private CodeRepository codeRepository;

    @Override
    public MixTokenDTO generateToken(String codeValue) {
        CodeDO codeDO = codeRepository.getOne(new LambdaUpdateWrapper<CodeDO>()
            .eq(CodeDO::getValue, codeValue)
            .eq(CodeDO::getStatus, CodeStatusEnum.VALID)
            .ge(CodeDO::getExpireDate, new Date()));

        // 无目标Code
        if (codeDO == null) {
            return new MixTokenDTO("", "", "", 0, false,
                "EXPIRED CODE OR INVALID CODE VALUE");
        }
        codeDO.setStatus(CodeStatusEnum.INVALID);
        // 更新目标Code状态失败
        if (!codeRepository.updateById(codeDO)) {
            return new MixTokenDTO("", "", "", 0, false,
                "EXPIRED CODE OR INVALID CODE VALUE");
        }

        // 生成两个token
        String authorizationToken = null;
        String refreshToken = null;
        int tryTime = 0;

        do {
            String tempToken = RandomUtil.generateToken(TOKEN_LENGTH);
            Date tempSecondsLater = DateUtil.generateDateAfterSeconds(EXPIRE_SECONDS);
            if (tokenRepository.save(new TokenDO(tempToken, TokenStatusEnum.WORK, tempSecondsLater))) {
                authorizationToken = tempToken;
                break;
            }
            tryTime++;
        } while (tryTime >= MAX_AUTHORIZATION_TOKEN_TRY_TIME);
        if (tryTime >= MAX_AUTHORIZATION_TOKEN_TRY_TIME) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return new MixTokenDTO("", "", "", 0, false,
                "INTERNAL ERROR");
        }

        do {
            String tempToken = RandomUtil.generateToken(TOKEN_LENGTH);
            Date tempSecondsLater = DateUtil.generateDateAfterSeconds(EXPIRE_SECONDS);
            if (tokenRepository.save(new TokenDO(tempToken, TokenStatusEnum.REFRESH, tempSecondsLater))) {
                refreshToken = tempToken;
                break;
            }
            tryTime++;
        } while (tryTime >= MAX_REFRESH_TOKEN_TRY_TIME);
        if (tryTime >= MAX_REFRESH_TOKEN_TRY_TIME) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return new MixTokenDTO("", "", "", 0, false,
                "INTERNAL ERROR");
        }
        return new MixTokenDTO(authorizationToken, refreshToken, TOKEN_TYPE, EXPIRE_SECONDS, true, "");
    }

    @Override
    public MixTokenDTO refreshToken(String refreshTokenValue) {
        TokenDO oldRefreshToken = tokenRepository.getOne(new LambdaQueryWrapper<TokenDO>()
            .eq(TokenDO::getValue, refreshTokenValue)
            .eq(TokenDO::getStatus, TokenStatusEnum.REFRESH)
        );
        // 无目标刷新Token
        if (oldRefreshToken == null) {
            return new MixTokenDTO("", "", "", 0, false,
                "INVALID REFRESH TOKEN");
        }
        oldRefreshToken.setStatus(TokenStatusEnum.WORK);
        oldRefreshToken.setExpireDate(DateUtil.generateDateAfterSeconds(EXPIRE_SECONDS));
        // 更新失败
        if (!tokenRepository.updateById(oldRefreshToken)) {
            return new MixTokenDTO("", "", "", 0, false,
                "INVALID REFRESH TOKEN");
        }

        // 生成新刷新token

        String newRefreshToken = null;
        int tryTime = 0;
        do {
            String tempToken = RandomUtil.generateToken(TOKEN_LENGTH);
            Date tempSecondsLater = DateUtil.generateDateAfterSeconds(EXPIRE_SECONDS);
            if (tokenRepository.save(new TokenDO(tempToken, TokenStatusEnum.REFRESH, tempSecondsLater))) {
                newRefreshToken = tempToken;
                break;
            }
            tryTime++;
        } while (tryTime >= MAX_REFRESH_TOKEN_TRY_TIME);
        if (tryTime >= MAX_REFRESH_TOKEN_TRY_TIME) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return new MixTokenDTO("", "", "", 0, false,
                "INTERNAL ERROR");
        }
        return new MixTokenDTO(refreshTokenValue, newRefreshToken, TOKEN_TYPE, EXPIRE_SECONDS, true, "");
    }

    @Override
    public boolean isValid(String content) {
        return tokenRepository.count(
            new LambdaQueryWrapper<TokenDO>()
                .eq(TokenDO::getValue, content)
                .eq(TokenDO::getStatus, TokenStatusEnum.WORK)
                .ge(TokenDO::getExpireDate, new Date())
        ) == 1;
    }
}
